package nbcp.base.config

import io.swagger.annotations.Api
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.springframework.beans.factory.config.BeanPostProcessor
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware
import org.springframework.core.DefaultParameterNameDiscoverer
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import nbcp.base.comm.OpenAction

import nbcp.base.*
import nbcp.base.comm.JsonMap
import nbcp.base.comm.StringMap
import nbcp.base.extend.*
import nbcp.base.utils.SpringUtil
import nbcp.comm.JsonpMapping
import nbcp.db.*
import nbcp.web.HttpContext
import nbcp.web.LoginUser
import nbcp.web.MyHttpRequestWrapper
import nbcp.web.MyHttpResponseWrapper
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import javax.servlet.http.HttpSession

@Component
class ActionDocBeanGather : BeanPostProcessor, ApplicationContextAware {
    companion object {
        var map = mutableMapOf<String, ActionDoc>()
    }

    private lateinit var context: ApplicationContext

    private lateinit var parameterNameDiscoverer: DefaultParameterNameDiscoverer

    override fun setApplicationContext(context: ApplicationContext) {
        this.context = context;
        this.parameterNameDiscoverer = DefaultParameterNameDiscoverer();
    }

    fun isProxyedBean(beanType: Class<*>): Boolean {
        return beanType.declaredFields.filter { it.name == "CGLIB\$BOUND" }.any()
    }

    fun getRealBean(beanType: Class<*>): Class<*> {
        if (isProxyedBean(beanType)) return getRealBean(beanType.superclass)
        return beanType;
    }


    data class ActionDoc(
            var remark: String = "",
            var routings: String = "",
            var methods: String = "",
            var parameters: StringMap = StringMap(),
            var returnType: String = ""
    ) {
        private var models: JsonMap = JsonMap();

        fun addModel(value: Class<*>) {
            if (this.models.containsKey(value.name)) {
                return;
            }

            var item = JsonMap();
            models.put(value.name, item);
            if (value.isEnum) {
                var f = value.GetEnumStringField();
                value.GetEnumList().forEach {
                    var remark = "";
                    if( f!= null){
                        f.isAccessible = true;
                        remark = f.get(it).AsString();
                    }
                    item.put(it.toString(), remark)
                }
                return;
            }
            value.AllFields.forEach {
                item.set(it.name, it.type.name);

                if (it.type.isEnum) {
                    addModel(it.type)
                    return@forEach
                }

                if (it.type.IsSimpleType() == false) {
                    addModel(it.type)
                    return@forEach
                }
            }
        }
    }


    override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? {
        var real_bean_type = getRealBean(bean.javaClass)
        if (real_bean_type == null) {
            return super.postProcessAfterInitialization(bean, beanName)
        }

        if (real_bean_type.annotations.filter {
                    it is RestController ||
                            it is Controller
                }.any()) {
            var doc = gatherBean(bean, real_bean_type);
        }

        return super.postProcessAfterInitialization(bean, beanName)
    }

    private fun gatherBean(bean: Any, real_bean_type: Class<*>) {
        var controller_remark = "";
        var controller_method = "";
        var controller_routing = "";

        kotlin.run {
            var bean_api = real_bean_type.annotations.filter {
                it is Api
            }.firstOrNull();
            if (bean_api != null) {
                var api = bean_api as Api
                controller_remark = arrayOf(api.tags.joinToString(","), api.value).joinToString(",")
            }
        }

        kotlin.run {
            var bean_mapping = real_bean_type.annotations.filter {
                it is JsonpMapping ||
                        it is GetMapping ||
                        it is PostMapping ||
                        it is RequestMapping
            }.firstOrNull();

            if (bean_mapping != null) {
                if (bean_mapping is JsonpMapping) {
                    controller_routing = bean_mapping.value.joinToString(",")
                    controller_method = "post"
                } else if (bean_mapping is GetMapping) {
                    controller_routing = bean_mapping.value.joinToString(",")
                    controller_method = "get"
                } else if (bean_mapping is PostMapping) {
                    controller_routing = bean_mapping.value.joinToString(",")
                    controller_method = "post"
                } else if (bean_mapping is RequestMapping) {
                    controller_routing = bean_mapping.value.joinToString(",")
                    controller_method = bean_mapping.method.map { it.name }.joinToString(",")
                }
            }
        }


        real_bean_type.methods.forEach {
            if (it.name.endsWith("\$default")) {
                return@forEach
            }


            var action_mapping = it.annotations.filter {
                it is JsonpMapping ||
                        it is GetMapping ||
                        it is PostMapping ||
                        it is RequestMapping
            }.firstOrNull()
            if (action_mapping == null) {
                return@forEach
            }

            var actionDoc = ActionDoc();

            kotlin.run {
                var bean_api = it.annotations.filter {
                    it is Api
                }.firstOrNull();
                if (bean_api != null) {
                    var api = bean_api as Api
                    actionDoc.remark = api.tags.joinToString(",") + ": " + api.value
                }
            }

            if (action_mapping is JsonpMapping) {
                actionDoc.routings = controller_routing + action_mapping.value.joinToString(",")
                actionDoc.methods = "post"
            } else if (action_mapping is GetMapping) {
                actionDoc.routings = controller_routing + action_mapping.value.joinToString(",")
                actionDoc.methods = "get"
            } else if (action_mapping is PostMapping) {
                actionDoc.routings = controller_routing + action_mapping.value.joinToString(",")
                actionDoc.methods = "post"
            } else if (action_mapping is RequestMapping) {
                actionDoc.routings = controller_routing + action_mapping.value.joinToString(",")
                actionDoc.methods = action_mapping.method.map { it.name }.joinToString(",")
            }

            //参数
            actionDoc.returnType = it.genericReturnType.typeName

            if (it.genericReturnType is ParameterizedTypeImpl) {
                var ret_type_args = (it.genericReturnType as ParameterizedTypeImpl).actualTypeArguments

                ret_type_args.forEach {
                    if (it is Class<*>) {
                        if (it.isEnum) {
                            actionDoc.addModel(it)
                            return@forEach
                        }
                        if (it.IsSimpleType()) {
                            return@forEach
                        }
                        actionDoc.addModel(it)
                    } else {
                        println(it.typeName)
                    }
                }
            }

            var parameterNames = parameterNameDiscoverer.getParameterNames(it)

            it.parameterTypes.forEachIndexed { index, pt ->
                if (HttpServletRequest::class.java.isAssignableFrom(pt) ||
                        HttpServletResponse::class.java.isAssignableFrom(pt) ||
                        HttpSession::class.java.isAssignableFrom(pt)) {
                    return@forEachIndexed
                }

                actionDoc.parameters.set(parameterNames.get(index), pt.name)

                if (pt.isEnum) {
                    actionDoc.addModel(pt);
                }

                if (pt.IsSimpleType() == false) {
                    actionDoc.addModel(pt);
                }
            }

            map.set(actionDoc.methods + " " + actionDoc.routings, actionDoc)
        }
    }
}